﻿using System.Drawing;
using System.Runtime.InteropServices;

using Vanara.Extensions;
using Vanara.PInvoke;

namespace CSharpBorderlessForm;
using static Vanara.PInvoke.DwmApi;
using static Vanara.PInvoke.Gdi32;
using static Vanara.PInvoke.User32;


public class Win32WindowCore
{
    private bool _borderless = true;
    private bool _resizable = true;
    private bool _dragable = true;
    private bool _borderlessShadow = true;
    private enum HitTestNCARegionMask : byte
    {
        client = 0b0000,
        left = 0b0001,
        right = 0b0010,
        top = 0b0100,
        bottom = 0b1000,
    }


    HWND _handle;

    public nint Handle => _handle.DangerousGetHandle();

    internal const WindowStyles WINDOWED_STYLE = WindowStyles.WS_OVERLAPPEDWINDOW | WindowStyles.WS_THICKFRAME | WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX;

    internal const WindowStyles DWM_BORDERLESS_STYLE = WindowStyles.WS_POPUP | WindowStyles.WS_THICKFRAME | WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX;

    internal const WindowStyles BASIC_BORDERLESS_STYLE = WindowStyles.WS_POPUP | WindowStyles.WS_THICKFRAME | WindowStyles.WS_SYSMENU | WindowStyles.WS_MINIMIZEBOX | WindowStyles.WS_MAXIMIZEBOX;

    private bool IsMaximized(HWND hwnd)
    {
        WINDOWPLACEMENT placement = new();

        if (!GetWindowPlacement(hwnd, ref placement))
        {
            return false;
        }

        return placement.showCmd == ShowWindowCommand.SW_MAXIMIZE;

    }

    private WindowStyles SelectBorderStyle()
    {

        return IsCompositonEnabled() ? DWM_BORDERLESS_STYLE : BASIC_BORDERLESS_STYLE;

    }

    private bool IsCompositonEnabled()
    {
        var success = DwmIsCompositionEnabled(out var pfEnabled);



        return success.Succeeded && pfEnabled;
    }

    private void SetShadow(HWND hwnd, bool enable)
    {
        MARGINS[] shadow = new MARGINS[] {
            new MARGINS(0),
            new MARGINS(0,0,1,0)
        };

        if (IsCompositonEnabled())
        {
            DwmExtendFrameIntoClientArea(hwnd, shadow[enable ? 1 : 0]);
        }
    }


    private void AdjustMaximizedClientRect(HWND hwnd, ref RECT rect)
    {
        if (!IsMaximized(hwnd)) return;

        var monitor = MonitorFromWindow(hwnd, MonitorFlags.MONITOR_DEFAULTTONULL);

        if (monitor.IsNull) return;

        MONITORINFO monitorInfo = new();

        if (!GetMonitorInfo(monitor, ref monitorInfo)) return;

        rect = monitorInfo.rcWork;
    }


    internal HWND CreateWindow(WindowProc wndproc)
    {
        var windowClass = WindowClass.MakeVisibleWindowClass("WinFormiumWindowClass", wndproc);
        return new VisibleWindow(windowClass, "WinFormiumWindow", null, null, WINDOWED_STYLE, WindowStylesEx.WS_EX_NOREDIRECTIONBITMAP);
    }

    nint WndProc(HWND hwnd, uint umsg, nint wParam, nint lParam)
    {



        var msg = (WindowMessage)umsg;

        switch (msg)
        {
            case WindowMessage.WM_NCCALCSIZE:
                {
                    if (wParam == 1 && _borderless)
                    {



                        var param = Marshal.PtrToStructure<NCCALCSIZE_PARAMS>(lParam);
                        AdjustMaximizedClientRect(hwnd, ref param.rgrc[0]);

                        return 0;
                    }
                }
                break;
            case WindowMessage.WM_NCACTIVATE:
                {
                    if (!IsCompositonEnabled())
                    {
                        return 1;
                    }
                }
                break;
            case WindowMessage.WM_CLOSE:
                {
                    DestroyWindow(hwnd);

                    Closed?.Invoke(this, EventArgs.Empty);

                    return 0;
                }
            //case WindowMessage.WM_DESTROY:
            //    {
            //        PostQuitMessage(0);
            //        return 0;
            //    }
            case WindowMessage.WM_PAINT:
                {

                    var hdc = BeginPaint(hwnd, out var ps);

                    string? text = "Hello, BorderlessWindow!";

                    TextOut(hdc, 20, 20, text, text.Length);

                    EndPaint(hwnd, ps);

                    return 0;
                }
            case WindowMessage.WM_DPICHANGED:
                {
                    var dpi = (int)Macros.HIWORD(wParam);
                    var rect = lParam.ToStructure<RECT>();
                    SetWindowPos(hwnd, HWND.NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED);



                }
                break;
            case WindowMessage.WM_NCHITTEST:
                {
                    if (_borderless)
                    {
                        return (nint)HitTestNCA(lParam);
                    }
                }
                break;
            case WindowMessage.WM_KEYDOWN:
            case WindowMessage.WM_SYSKEYDOWN:
                {
                    var key = (VK)wParam.ToInt32();
                    switch (key)
                    {
                        case VK.VK_F1: { SetBorderless(!_borderless); } return 0;
                        case VK.VK_F2: { SetBorderlessShadow(!_borderlessShadow); } return 0;
                        case VK.VK_F3: { _dragable = !_dragable; } return 0;
                        case VK.VK_F4: { _resizable = !_resizable; } return 0;
                    }
                }

                break;
        }

        return DefWindowProc(hwnd, umsg, wParam, lParam);
    }


    public event EventHandler? Closed;


    private HitTestValues HitTestNCA(nint lParam)
    {
        var cursor = new POINT(lParam.ToInt32());

        var border = new POINT(GetSystemMetrics(SystemMetric.SM_CXFRAME) + GetSystemMetrics(SystemMetric.SM_CXPADDEDBORDER), GetSystemMetrics(SystemMetric.SM_CYFRAME) + GetSystemMetrics(SystemMetric.SM_CXPADDEDBORDER));



        if (!GetWindowRect(_handle, out var windowRect))
        {
            return HitTestValues.HTNOWHERE;
        }

        var drag = _dragable ? HitTestValues.HTCAPTION : HitTestValues.HTCLIENT;

        var result =
        (byte)HitTestNCARegionMask.left * (cursor.x < (windowRect.left + border.x) ? 1 : 0) |
        (byte)HitTestNCARegionMask.right * (cursor.x >= (windowRect.right - border.x) ? 1 : 0) |
        (byte)HitTestNCARegionMask.top * (cursor.y < (windowRect.top + border.y) ? 1 : 0) |
        (byte)HitTestNCARegionMask.bottom * (cursor.y >= (windowRect.bottom - border.y) ? 1 : 0);

        switch (result)
        {
            case (byte)HitTestNCARegionMask.left:
                return _resizable ? HitTestValues.HTLEFT : drag;
            case (byte)HitTestNCARegionMask.right:
                return _resizable ? HitTestValues.HTRIGHT : drag;
            case (byte)HitTestNCARegionMask.top:
                return _resizable ? HitTestValues.HTTOP : drag;
            case (byte)HitTestNCARegionMask.bottom:
                return _resizable ? HitTestValues.HTBOTTOM : drag;
            case (byte)(HitTestNCARegionMask.top | HitTestNCARegionMask.left):
                return _resizable ? HitTestValues.HTTOPLEFT : drag;
            case (byte)(HitTestNCARegionMask.top | HitTestNCARegionMask.right):
                return _resizable ? HitTestValues.HTTOPRIGHT : drag;
            case (byte)(HitTestNCARegionMask.bottom | HitTestNCARegionMask.left):
                return _resizable ? HitTestValues.HTBOTTOMLEFT : drag;
            case (byte)(HitTestNCARegionMask.bottom | HitTestNCARegionMask.right):
                return _resizable ? HitTestValues.HTBOTTOMRIGHT : drag;
            case (byte)HitTestNCARegionMask.client:
                return drag;
            default:
                return HitTestValues.HTNOWHERE;
        }
    }

    public Win32WindowCore()
    {
        _handle = CreateWindow(WndProc);

        SetBorderless(_borderless);
        SetBorderlessShadow(_borderlessShadow);

        // 这两行导致 AOT 运行时错误
        // 详见：
        // https://github.com/dahall/Vanara/issues/527

        var backdropType = (Int32)DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TRANSIENTWINDOW;

        var size = sizeof(Int32);
        var ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(backdropType, ptr, false);

        DwmSetWindowAttribute(_handle, DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE, ptr,size);

        Marshal.FreeHGlobal(ptr);

        ShowWindow(_handle, ShowWindowCommand.SW_SHOW);
    }



    public void SetBorderless(bool enable)
    {
        var newStyle = enable ? SelectBorderStyle() : WINDOWED_STYLE;

        var oldStyle = (WindowStyles)(uint)GetWindowLong(_handle, WindowLongFlags.GWL_STYLE);

        if (newStyle != oldStyle)
        {
            _borderless = enable;

            SetWindowLong(_handle, WindowLongFlags.GWL_STYLE, (IntPtr)(uint)newStyle);

            SetShadow(_handle, _borderlessShadow && newStyle != WINDOWED_STYLE);

            SetWindowPos(_handle, HWND.NULL, 0, 0, 0, 0, SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE);

            ShowWindow(_handle, ShowWindowCommand.SW_SHOW);
        }
    }



    public void SetBorderlessShadow(bool enable)
    {
        if (_borderless)
        {
            _borderlessShadow = enable;
            SetShadow(_handle, enable);
        }
    }


}
